"""
Data collection service

Unified data collection interface for all device types
"""

from __future__ import annotations

import logging
from typing import Any

from app.connectors.base import BaseConnector, DataPoint
from app.connectors.factory import DeviceConnectorFactory, DeviceType
from app.services.metrics_service import MetricsService


logger = logging.getLogger(__name__)


class DataCollectionService:
    """
    Unified data collection service

    Responsibilities:
    1. Manage device connector lifecycle
    2. Unified data collection interface
    3. Unified data storage interface
    4. Unified exception handling
    """

    def __init__(self) -> None:
        # Connector cache {device_id: connector}
        self._connectors: dict[int, BaseConnector] = {}

    def get_cached_connector(self, device_id: int) -> BaseConnector | None:
        """
        Get cached connector

        Args:
            device_id: Device ID

        Returns:
            BaseConnector | None: Cached connector or None
        """
        return self._connectors.get(device_id)

    async def create_connector(
        self,
        device_id: int,
        device_type: DeviceType,
        config: dict[str, Any]
    ) -> BaseConnector:
        """
        Create and cache a connector

        Args:
            device_id: Device ID
            device_type: Device type
            config: Connection configuration

        Returns:
            BaseConnector: Connector instance
        """
        # Check cache
        if device_id in self._connectors:
            connector = self._connectors[device_id]
            # Verify connection health
            if await connector.health_check():
                logger.debug(f"Using cached connector for device {device_id}")
                return connector
            else:
                # Connection invalid, remove from cache
                logger.warning(f"Cached connector for device {device_id} is unhealthy, recreating")
                await connector.disconnect()
                del self._connectors[device_id]

        # Ensure device_id in config
        config["device_id"] = device_id

        # Create connector
        connector = DeviceConnectorFactory.create(device_type, config)

        # Connect
        await connector.connect()

        # Cache
        self._connectors[device_id] = connector

        logger.info(f"Created and cached connector for device {device_id}")
        return connector

    async def collect_single_point(
        self,
        db: Any,  # AsyncSession
        device_id: int,
        device_type: DeviceType,
        config: dict[str, Any],
        metric_name: str,
        address: str
    ) -> DataPoint:
        """
        Collect single data point (unified interface)

        Args:
            db: Database session
            device_id: Device ID
            device_type: Device type
            config: Connection configuration
            metric_name: Metric name
            address: Data address

        Returns:
            DataPoint: Data point
        """
        # Get or create connector
        connector = await self.create_connector(device_id, device_type, config)

        # Read data
        data_point = await connector.read_single(address)

        # Save to database
        await MetricsService.save_metric(
            db=db,
            device_id=device_id,
            metric_name=metric_name,
            value=data_point.value,
            timestamp=data_point.timestamp,
            quality=data_point.quality.value,
            unit=data_point.unit
        )

        logger.info(
            f"Collected single point: device={device_id}, "
            f"metric={metric_name}, value={data_point.value}"
        )

        return data_point

    async def collect_multiple_points(
        self,
        db: Any,  # AsyncSession
        device_id: int,
        device_type: DeviceType,
        config: dict[str, Any],
        points: list[dict[str, Any]]
    ) -> list[DataPoint]:
        """
        Collect multiple data points

        Args:
            db: Database session
            device_id: Device ID
            device_type: Device type
            config: Connection configuration
            points: List of points [{"name": "temp", "address": "...", "unit": "°C"}, ...]

        Returns:
            List[DataPoint]: List of data points
        """
        # Get or create connector
        connector = await self.create_connector(device_id, device_type, config)

        # Extract addresses
        addresses = [point["address"] for point in points]

        # Batch read
        data_points = await connector.read_multiple(addresses)

        # Batch save
        for i, data_point in enumerate(data_points):
            point_config = points[i]
            try:
                await MetricsService.save_metric(
                    db=db,
                    device_id=device_id,
                    metric_name=point_config["name"],
                    value=data_point.value,
                    timestamp=data_point.timestamp,
                    quality=data_point.quality.value,
                    unit=point_config.get("unit")
                )
            except Exception as e:
                logger.error(
                    f"Failed to save metric {point_config['name']}: {e}"
                )

        logger.info(
            f"Collected multiple points: device={device_id}, "
            f"count={len(data_points)}"
        )

        return data_points

    async def write_value(
        self,
        device_id: int,
        device_type: DeviceType,
        config: dict[str, Any],
        address: str,
        value: Any
    ) -> bool:
        """
        Write data point (unified interface)

        Args:
            device_id: Device ID
            device_type: Device type
            config: Connection configuration
            address: Data address
            value: Value to write

        Returns:
            bool: Write success
        """
        # Get or create connector
        connector = await self.create_connector(device_id, device_type, config)

        # Write
        result = await connector.write_single(address, value)

        logger.info(
            f"Write value: device={device_id}, "
            f"address={address}, value={value}, success={result}"
        )

        return result

    async def disconnect_device(self, device_id: int) -> bool:
        """
        Disconnect specific device

        Args:
            device_id: Device ID

        Returns:
            bool: Disconnect success
        """
        if device_id in self._connectors:
            connector = self._connectors[device_id]
            try:
                await connector.disconnect()
                del self._connectors[device_id]
                logger.info(f"Disconnected device {device_id}")
                return True
            except Exception as e:
                logger.error(f"Failed to disconnect device {device_id}: {e}")
                return False

        logger.warning(f"Device {device_id} not in cache")
        return False

    async def disconnect_all(self) -> None:
        """Disconnect all connections (called on application shutdown)"""
        logger.info(f"Disconnecting all devices ({len(self._connectors)} active)")

        for device_id, connector in list(self._connectors.items()):
            try:
                await connector.disconnect()
                logger.debug(f"Disconnected device {device_id}")
            except Exception as e:
                logger.error(f"Failed to disconnect device {device_id}: {e}")

        self._connectors.clear()
        logger.info("All devices disconnected")

    def get_active_connections(self) -> list[int]:
        """
        Get list of active device IDs

        Returns:
            List[int]: List of device IDs with active connections
        """
        return list(self._connectors.keys())

    def get_connection_count(self) -> int:
        """
        Get count of active connections

        Returns:
            int: Number of active connections
        """
        return len(self._connectors)


# Global service instance
data_collection_service = DataCollectionService()
